package org.example;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Properties;

import static org.objectweb.asm.Opcodes.*;

/**
 * 演示: 没有手写源代码,完全通过asm程序生成一个asmTest/target/classes/Duck.class/Duck.class文件
 * 使用asm编码方式很困难,我们可以先正向手写一个Duck类,通过ASM bytecode viewer插件来生成
 * asm代码
 *
 */
@SuppressWarnings("all")
public class CodeGenerationUtil {

    public static void generateClassFile(String packageInfo,String clazzName) throws Exception {
        //ClassReader用于读取原有字节码，ClassWriter用于写入字节码，
        ClassWriter classWriter = new ClassWriter(0);
        //通过vist确定类的同步信息 java版本号 类修饰符 类的权限定名

        //将 com.scy.example.Duck ---> com/scy/example/Duck
        String qualifiedPathName = dot2Slash(packageInfo)+"/"+clazzName;

        classWriter.visit(Opcodes.V1_8, ACC_PUBLIC, qualifiedPathName,
                null, "java/lang/Object", null);

        //构造函数
        MethodVisitor methodVisitor4NoneConstructor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        methodVisitor4NoneConstructor.visitCode();
        methodVisitor4NoneConstructor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor4NoneConstructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
        methodVisitor4NoneConstructor.visitInsn(RETURN);
        methodVisitor4NoneConstructor.visitMaxs(1, 1);
        methodVisitor4NoneConstructor.visitEnd();

        /**
         * private static Type getTypeInternal(
         *       final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) {
         *     switch (descriptorBuffer.charAt(descriptorBegin)) {
         *       case 'V':
         *         return VOID_TYPE;
         *       case 'Z':
         *         return BOOLEAN_TYPE;
         *       case 'C':
         *         return CHAR_TYPE;
         *       case 'B':
         *         return BYTE_TYPE;
         *       case 'S':
         *         return SHORT_TYPE;
         *       case 'I':
         *         return INT_TYPE;
         *       case 'F':
         *         return FLOAT_TYPE;
         *       case 'J':
         *         return LONG_TYPE;
         *       case 'D':
         *         return DOUBLE_TYPE;
         *       case '[':
         *         return new Type(ARRAY, descriptorBuffer, descriptorBegin, descriptorEnd);
         *       case 'L':
         *         return new Type(OBJECT, descriptorBuffer, descriptorBegin + 1, descriptorEnd - 1);
         *       case '(':
         *         return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd);
         *       default:
         *         throw new IllegalArgumentException();
         *
         */

        // 定义sayMethod方法
//        MethodVisitor methodVisitor4sayMethod = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sayMethod", "(IZ)V", null, null);
////        MethodVisitor methodVisitor4sayMethod = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sayMethod", "(Ljava/lang/String;Ljava/lang/String;)V", null, null);
//        methodVisitor4sayMethod.visitCode();
//        methodVisitor4sayMethod.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
//        methodVisitor4sayMethod.visitLdcInsn("Duck类中的sayMethod方法执行了,I'm a Duck,Just quark gaga.....");
//        methodVisitor4sayMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
//        methodVisitor4sayMethod.visitInsn(Opcodes.RETURN);
//        methodVisitor4sayMethod.visitMaxs(2, 2);
//        methodVisitor4sayMethod.visitEnd();

        MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "sayMethod", "(IZLjava/lang/String;)Ljava/lang/String;", null, null);
        methodVisitor.visitCode();
        Label label0 = new Label();
        methodVisitor.visitLabel(label0);
        methodVisitor.visitLineNumber(5, label0);
        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
        methodVisitor.visitLdcInsn("Ducke类中的sayMethod方法执行了,firstArg=");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitVarInsn(ILOAD, 1);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitLdcInsn(", sencondArg=");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitVarInsn(ILOAD, 2);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Z)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitLdcInsn(", ThirdArg=");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitVarInsn(ALOAD, 3);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        Label label1 = new Label();
        methodVisitor.visitLabel(label1);
        methodVisitor.visitLineNumber(6, label1);
        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        methodVisitor.visitLdcInsn("Duck...sayMethod is running");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        Label label2 = new Label();
        methodVisitor.visitLabel(label2);
        methodVisitor.visitLineNumber(7, label2);
        methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
        methodVisitor.visitVarInsn(ALOAD, 3);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitLdcInsn(" is Duck,age is ");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitVarInsn(ILOAD, 1);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
        methodVisitor.visitInsn(ARETURN);
        Label label3 = new Label();
        methodVisitor.visitLabel(label3);
        methodVisitor.visitLocalVariable("this", "Lorg/example/Duck;", null, label0, label3, 0);
        methodVisitor.visitLocalVariable("age", "I", null, label0, label3, 1);
        methodVisitor.visitLocalVariable("b", "Z", null, label0, label3, 2);
        methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label3, 3);
        methodVisitor.visitMaxs(3, 4);
        methodVisitor.visitEnd();

        // ClassWriter cs结束操作
        classWriter.visitEnd();

        // 将classWriter classWriter 转换成字节数组写到文件里面去
        byte[] data = classWriter.toByteArray();
        File packageAbsoluteDir = new File(readConf("conf.properties") + "/" + dot2Slash(packageInfo));

        if (!packageAbsoluteDir.exists()) {
            packageAbsoluteDir.mkdirs();
        }

        File file = new File(packageAbsoluteDir.getAbsolutePath() + "/" + clazzName+".class");
        FileOutputStream fout = new FileOutputStream(file);
        fout.write(data);
        fout.close();
        System.out.println("由代码生成的class文件成功了,在target目中的"+dot2Slash(packageInfo)+"/"+clazzName+".class");
    }

    public static String dot2Slash(String src) {
        if (src.contains(".")) {
//            String replace = src.replace(".", "/");
            return  src.replace(".", "/");
        } else {
            return src;
        }
    }

    public static String readConf(String name) throws Exception{
        Properties properties = new Properties();
        InputStream resource = CodeGenerationUtil.class.getClassLoader().getResourceAsStream(name);

        properties.load(resource);
        String path4genClass = properties.getProperty("path4genClass");

        return path4genClass;
    }

}